#include <stdio.h>
#include <unistd.h>
#include "defines.h"

/**
 * Forward declaration of our test functions
 */
void* testfunc1(void*);
void* testfunc2(void*);
void* testfunc3(void*);
void* testfunc4(void*);

/**
 *  Part B requires implementing locks and condition variables
 *
 *    thread_mutex_t
 *    THREAD_MUTEX_INITIALIZER
 *    thread_mutex_lock(thread_mutex_t*)
 *    thread_mutex_unlock(thread_mutex_t*)
 *    thread_cond_t
 *    THREAD_COND_INITIALIZER
 *    thread_cond_wait(thread_cond_t*, thread_mutex_t*)
 *    thread_cond_signal(thread_cond_t*)
 */
int main()
{
    // make room for a bunch of uthreads
    THREAD_T tid[32];

    // Test #1: ensure locks work
    THREAD_CREATE(&tid[0], testfunc1, NULL);
    THREAD_CREATE(&tid[1], testfunc2, NULL);
    THREAD_JOIN(tid[0], NULL);
    THREAD_JOIN(tid[1], NULL);

    // Test #2: ensure condition variables work.  Example from
    // https://computing.llnl.gov/tutorials/pthreads/#ConditionVariables
    THREAD_CREATE(&tid[0], testfunc3, (void*)0);
    THREAD_CREATE(&tid[1], testfunc3, (void*)1);
    THREAD_CREATE(&tid[2], testfunc4, (void*)2);
    THREAD_JOIN(tid[0], NULL);
    THREAD_JOIN(tid[1], NULL);
    THREAD_JOIN(tid[2], NULL);
}

/**
 * Test locks by having one thread get a lock, and another wait on it
 */
THREAD_MUTEX_T lock = THREAD_MUTEX_INITIALIZER;
volatile int thread2ready = 0;
volatile int thread1ready = 0;

/**
 *  First thread grabs the lock and then yields
 */
void* testfunc1(void* params)
{
    // get the lock
    printf("Thread 1 is getting the lock\n");
    THREAD_MUTEX_LOCK(&lock);
    printf("Thread 1 has the lock\n");

    // keep yielding until we are sure the other thread is waiting...
    printf("Thread 1 is waiting for thread 2 to initialize\n");
    while (thread2ready == 0) {
        THREAD_YIELD();
    }
    printf("Thread 1 knows thread 2 is waiting\n");
    thread1ready = 1;
    printf("Thread 1 is sleeping for 5 seconds\n");
    sleep(5);

    // ok, now release the lock
    printf("Thread 1 is releasing the lock\n");
    THREAD_MUTEX_UNLOCK(&lock);
    printf("Thread 1 has released the lock\n");
    return NULL;
}

/**
 * Second thread tries to get the lock while it knows that the first thread
 * has it.
 */
void* testfunc2(void* params)
{
    thread2ready = 1;
    printf("Thread 2 is running\n");
    while (thread1ready == 0) {
        THREAD_YIELD();
    }
    printf("Thread 2 knows thread1 has the lock, but is trying to get it anyway\n");
    THREAD_MUTEX_LOCK(&lock);
    printf("Thread 2 has the lock\n");
    THREAD_MUTEX_UNLOCK(&lock);
    printf("Thread 2 released the lock\n");
    return NULL;
}

/**
 * Test condition variables by having threads wait on conditions that
 * indicate the state of a variable, where each thread can only increment the
 * variable when a precondition holds
 */
THREAD_MUTEX_T lock2 = THREAD_MUTEX_INITIALIZER;
THREAD_COND_T  cv2   = THREAD_COND_INITIALIZER;
int            count = 0;

/**
 *  Threads running this code will try to increment a counter 10 times.  Note
 *  that if a thread sees the counter is 12, it signals for another thread to
 *  wait.
 */
void* testfunc3(void* arg)
{
    // yield, to be sure all threads are running
    THREAD_YIELD();
    int i;
    for (i = 0; i < 10; ++i) {
        // get the lock, increment the counter
        THREAD_MUTEX_LOCK(&lock2);
        count++;
        printf("Thread %d incremented to %d\n", (int)arg, count);
        // while holding lock, should we wake the other thread?
        if (count == 12) {
            THREAD_COND_SIGNAL(&cv2);
            printf("Thread %d is signaling\n", (int)arg);
        }
        printf("Thread %d releasing lock\n", (int)arg);
        THREAD_MUTEX_UNLOCK(&lock2);

        // let someone else have a turn...
        sleep(1);
    }
    return 0;
}

/**
 * Threads running this code will wait for the counter to be >= 12, and then
 * will add 125 to the counter
 */
void* testfunc4(void* arg)
{
    // yield, to be sure all threads are running
    THREAD_YIELD();

    // get the lock, check if the counter is 12
    THREAD_MUTEX_LOCK(&lock2);
    while (count < 12) {
        printf("Thread %d waiting for counter to be above 12\n", (int)arg);
        THREAD_COND_WAIT(&cv2, &lock2);
        printf("Thread %d awake, with counter == %d\n", (int)arg, count);
        count += 125;
    }
    printf("Thread %d releasing lock\n", (int)arg);
    THREAD_MUTEX_UNLOCK(&lock2);
    return 0;
}

